package com.ums.upos.util;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.OperationCanceledException;
import android.text.TextUtils;
import android.view.WindowManager;

import com.nexgo.common.ByteUtils;
import com.nexgo.common.Des;
import com.nexgo.oaf.apiv3.SdkResult;
import com.nexgo.oaf.apiv3.device.beeper.Beeper;
import com.nexgo.oaf.apiv3.device.pinpad.CalcModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.InputModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.PinAlgorithmModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.PinKeyboardModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.PinPad;
import com.nexgo.oaf.apiv3.device.pinpad.WorkKeyTypeEnum;
import com.ums.upos.GlobalDataHolder;
import com.ums.upos.service.PinKeyCodeEvent;
import com.ums.upos.service.ui.PinInputDialog;
import com.ums.upos.uapi.R;
import com.ums.upos.uapi.ServiceResult;
import com.ums.upos.uapi.device.pinpad.DiversifyKeyType;
import com.ums.upos.uapi.device.pinpad.KSNConstrants;
import com.ums.upos.uapi.device.pinpad.PinPadKeyCode;

import org.greenrobot.eventbus.EventBus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subjects.BehaviorSubject;

/**
 * Created by xiaox on 15/11/26.
 */
public class PinPadUtils {
    private static Logger log = LoggerFactory.getLogger("PinpPadUtils");

    public final static int MKEY_EXTERNAL_MAX_INDEX = 100;
    public final static int MOBLILE_MODE_FIXED_MKEYIDX = 19;

    //手机模式下的三个出厂密钥
    //主密钥索引
    public static final int MAINKEY_IDX = 20;
    public static final int AUTHKEY_IDX = 21;
    public static final int APPKEY_IDX = 22;


    public static String getDiversifiedCryptData(Context context, int keyType, String factor, String orgData, boolean isEncrypt) {
        if (factor == null || factor.length() % 8 > 0 || orgData == null || orgData.length() == 0) {
            log.debug("入参有错误--->");
            return null;
        }
        PinPad sdkPinPad = GlobalDataHolder.getInstance().getDeviceEngine().getPinPad();
        boolean isOldSolution = false;//原方案为出厂灌入三个“主”密钥,现方案为灌入三个“工作”密钥,为了兼容以前的方案，这里要判断一下机器里面有没有旧方案的三个密钥,做兼容处理
        if (sdkPinPad.isKeyExist(PinPadUtils.MAINKEY_IDX)
                && sdkPinPad.isKeyExist(PinPadUtils.AUTHKEY_IDX)
                && sdkPinPad.isKeyExist(PinPadUtils.APPKEY_IDX)) {
            log.debug("存在旧方案的密钥");
            isOldSolution = true;
        }

        byte[] tmp = ByteUtils.hexString2ByteArray(factor);
        byte[] factorByte = new byte[16];
        System.arraycopy(tmp, 0, factorByte, 0, tmp.length);
        for (int i = 8; i < 16; i++) {
            factorByte[i] = (byte) (~factorByte[i - 8]);
        }
        byte[] key = new byte[16];
        byte[] orgDataByte = ByteUtils.hexString2ByteArray(orgData);
        byte[] data = new byte[((orgDataByte.length + 7) / 8) * 8];

        System.arraycopy(orgDataByte, 0, data, 0, orgDataByte.length);

        if (isOldSolution) {
            switch (keyType) {
                case DiversifyKeyType.MasterKey:
                    key = sdkPinPad.encryptByMKey(PinPadUtils.MAINKEY_IDX, factorByte, factorByte.length);
                    break;
                case DiversifyKeyType.VerifyKey:
                    key = sdkPinPad.encryptByMKey(PinPadUtils.AUTHKEY_IDX, factorByte, factorByte.length);
                    break;
                case DiversifyKeyType.AppKey:
                    key = sdkPinPad.encryptByMKey(PinPadUtils.APPKEY_IDX, factorByte, factorByte.length);
                    break;
                default:
                    break;
            }
        } else {
            switch (keyType) {
                case DiversifyKeyType.MasterKey:
                    key = sdkPinPad.calcByWKey(PinPadUtils.MOBLILE_MODE_FIXED_MKEYIDX, WorkKeyTypeEnum.PINKEY, factorByte, factorByte.length, CalcModeEnum.ENCRYPT);
                    break;
                case DiversifyKeyType.VerifyKey:
                    key = sdkPinPad.calcByWKey(PinPadUtils.MOBLILE_MODE_FIXED_MKEYIDX, WorkKeyTypeEnum.MACKEY, factorByte, factorByte.length, CalcModeEnum.ENCRYPT);
                    break;
                case DiversifyKeyType.AppKey:
                    key = sdkPinPad.calcByWKey(PinPadUtils.MOBLILE_MODE_FIXED_MKEYIDX, WorkKeyTypeEnum.TDKEY, factorByte, factorByte.length, CalcModeEnum.ENCRYPT);
                    break;
                default:
                    break;
            }

        }

        if (key != null) {
            log.debug("key {}", ByteUtils.byteArray2HexString(key));
            log.debug("data1 {}", ByteUtils.byteArray2HexString(data));
            if (isEncrypt) {
                data = Des.trides_crypt(key, data);
            } else {
                data = Des.trides_decrypt(key, data);
            }
            log.debug("data2 {}", ByteUtils.byteArray2HexString(data));
        }
        if (data != null) {
            return ByteUtils.byteArray2HexString(data).toUpperCase();
        }
        return null;
    }

    private static final String PIN_KSN_INIT = "0000000000000000";
    private static final String IC_KSN_INIT = "2000000000000000";
    private static final String TK_KSN_INIT = "1000000000000000";

    public static Bundle getKSN(Context context) {
        String tkKsn, icKsn, pinKsn;

        pinKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.PINKSN, PIN_KSN_INIT);
        log.debug("pinKsn {}", pinKsn);
        if (pinKsn.isEmpty() || pinKsn.length() != 16) {
            pinKsn = PIN_KSN_INIT;
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.TKKSN, pinKsn);
        }
        icKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.ICKSN, IC_KSN_INIT);
        log.debug("icKsn {}", icKsn);
        if (icKsn.isEmpty() || icKsn.length() != 16) {
            icKsn = IC_KSN_INIT;
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.ICKSN, icKsn);
        }
        tkKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.TKKSN, TK_KSN_INIT);
        log.debug("tkKsn {}", tkKsn);
        if (tkKsn.isEmpty() || tkKsn.length() != 16) {
            tkKsn = TK_KSN_INIT;
            log.debug("tkKsn {}", tkKsn);
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.TKKSN, tkKsn);
        }
        Bundle bundle = new Bundle();
        bundle.putString(KSNConstrants.PINKSN, pinKsn);
        bundle.putString(KSNConstrants.ICKSN, icKsn);
        bundle.putString(KSNConstrants.TKKSN, tkKsn);

        log.debug("return pinKsn {},icKsn {},tkKsn {}", pinKsn, icKsn, tkKsn);
        return bundle;
    }

    public static boolean increaseKSN(int ksnType, Context context) {
        String tkKsn, icKsn, pinKsn;
        long value;
        NumberFormat format = NumberFormat.getInstance();
        format.setMinimumIntegerDigits(16);
        format.setGroupingUsed(false);
        if (ksnType == KSNConstrants.TYPE_TKKSN) {
            tkKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.TKKSN);
            if (tkKsn.isEmpty()) {
                tkKsn = TK_KSN_INIT;
            }
            value = Long.valueOf(tkKsn) + 1;
            if (value >= 3 * Math.pow(10, 16)) {
                tkKsn = TK_KSN_INIT;
            } else {
                tkKsn = format.format(value);
            }
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.TKKSN, tkKsn);
            return true;
        } else if (ksnType == KSNConstrants.TYPE_ICKSN) {
            icKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.ICKSN);
            if (icKsn.isEmpty()) {
                icKsn = IC_KSN_INIT;
            }
            value = Long.valueOf(icKsn) + 1;
            if (value >= 2 * Math.pow(10, 16)) {
                icKsn = IC_KSN_INIT;
            } else {
                icKsn = format.format(value);
            }
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.ICKSN, icKsn);
            return true;
        } else if (ksnType == KSNConstrants.TYPE_APPKSN) {
            pinKsn = SharedPreferencesUtil.getInstance(context).getString(KSNConstrants.PINKSN);
            if (pinKsn.isEmpty()) {
                pinKsn = PIN_KSN_INIT;
            }
            value = Long.valueOf(pinKsn) + 1;
            if (value >= 1 * Math.pow(10, 16)) {
                pinKsn = PIN_KSN_INIT;
            } else {
                pinKsn = format.format(value);
            }
            SharedPreferencesUtil.getInstance(context).putString(KSNConstrants.PINKSN, pinKsn);
            return true;
        } else {
            return false;
        }
    }

    public static byte[] getMobileModeEnPin(byte[] panBlock, byte[] plainPin, Context context) {
        StringBuilder sb = new StringBuilder();
        Bundle bundle = PinPadUtils.getKSN(context);
        byte[] temp = new byte[14];
        Arrays.fill(temp, (byte) 'f');
        System.arraycopy(plainPin, 0, temp, 0, Math.min(plainPin.length, temp.length));
        sb.append(String.format("%02x", plainPin.length));
        sb.append(ByteUtils.asciiByteArray2String1(temp));

        byte[] pan = new byte[8];
        if(panBlock != null) {
            log.debug("panBlock {}", ByteUtils.byteArray2HexString(panBlock));
            //如果有卡号就异或一下
            String cardNo = ByteUtils.asciiByteArray2String1(panBlock);
            if (!TextUtils.isEmpty(cardNo) && cardNo.length() > 13) {
                cardNo = cardNo.substring(cardNo.length() - 13, cardNo.length() - 1);
                System.arraycopy(ByteUtils.hexString2ByteArray(cardNo), 0, pan, 2, 6);
            }
        }
        byte[] data = ByteUtils.xor8(ByteUtils.hexString2ByteArray(sb.toString()), pan);
        String enData = PinPadUtils.getDiversifiedCryptData(context, DiversifyKeyType.AppKey,
                bundle.getString(KSNConstrants.PINKSN),
                ByteUtils.byteArray2HexString(data),
                true);
        return ByteUtils.hexString2ByteArray(enData);
    }

    public interface OnPinInputListener {
        void onInputResult(int serviceResult, byte[] enPinData);

        void onSendKey(byte keyCode);
    }

    public static int inputOnlinePin(int[] pinLen, int timeout, final Context context, byte[] panBlock, final int mKeyId, final OnPinInputListener listener) {
        int result;
//        PinPad sdkPinPad = GlobalDataHolder.getInstance().getDeviceEngine().getPinPad();
        PinPad sdkPinPad = GlobalDataHolder.getInstance().getSdkPinpad();
        result = sdkPinPad.inputOnlinePin(pinLen, timeout, panBlock, mKeyId, PinAlgorithmModeEnum.ISO9564FMT1,
                new com.nexgo.oaf.apiv3.device.pinpad.OnPinPadInputListener() {
                    @Override
                    public void onInputResult(int i, byte[] bytes) {
                        int retCode;
                        log.debug("sdkPinPad.inputOnlinePin onInputResult:{}", i);
                        switch (i) {
                            case SdkResult.Success:
                                retCode = ServiceResult.Success;
                                break;
                            case SdkResult.PinPad_Input_Cancel:
                                retCode = ServiceResult.PinPad_Input_Cancel;
                                break;
                            case SdkResult.PinPad_No_Pin_Input:
                                retCode = ServiceResult.PinPad_No_Pin_Input;
                                break;
                            case SdkResult.PinPad_Input_Timeout:
                                retCode = ServiceResult.PinPad_Input_Timeout;
                                break;
                            default:
                                //mhl20171019 新增工作密钥是否存在判断
                                PinPad sdkPinPad = GlobalDataHolder.getInstance().getSdkPinpad();
                                if(!sdkPinPad.isKeyExist(mKeyId, WorkKeyTypeEnum.PINKEY))
                                    retCode = ServiceResult.PinPad_No_Key_Error;
                                else
                                    retCode = ServiceResult.PinPad_Other_Error;
                                break;
                        }
                        if (retCode == ServiceResult.Success) {
                            EventBus.getDefault().post(new PinKeyCodeEvent(PinPadKeyCode.KEYCODE_CONFIRM));
                        } else {
                            EventBus.getDefault().post(new PinKeyCodeEvent(PinPadKeyCode.KEYCODE_CANCEL));
                        }
                        listener.onInputResult(retCode, bytes);
                    }

                    @Override
                    public void onSendKey(byte b) {
                        EventBus.getDefault().post(new PinKeyCodeEvent(b));
                        listener.onSendKey(b);
                    }
                });
        log.debug("sdkPinPad.inputOnlinePin result:{}", result);
        if (result == SdkResult.Success) {
            //发起输入密码界面
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    PinInputDialog pinInputDialog = new PinInputDialog(context, R.style.CustomDialog);
                    pinInputDialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
                    pinInputDialog.setCanceledOnTouchOutside(false);
                    pinInputDialog.show();
                    pinInputDialog.setInputPinType(true);
                }
            });
            return ServiceResult.Success;
        } else {
            return ServiceResult.Fail;
        }
    }

    public static int inputMobileModeOnlinePin(final byte[] panBlock, final int timeout, final Context context, final OnPinInputListener listener) {
        //手机模式下是明文的,实体键盘与触摸键盘要分开处理
        final BehaviorSubject<Byte> subject = BehaviorSubject.create();
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final int finalMax = 12;
        subject.asObservable()
                .observeOn(AndroidSchedulers.mainThread())
                .timeout(timeout, TimeUnit.SECONDS)
                .doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        //发起输入密码界面
                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                                PinInputDialog pinInputDialog = new PinInputDialog(context, R.style.CustomDialog);
                                pinInputDialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
                                pinInputDialog.setCanceledOnTouchOutside(false);
                                pinInputDialog.show();
                                pinInputDialog.setInputPinType(true);
                                if (MachineModelUtils.isVirtualPinPad()) {
                                    pinInputDialog.showKeyboad();
                                    pinInputDialog.setOnKeyboadClickListener(new PinInputDialog.OnKeyboadClickListener() {
                                        @Override
                                        public void onSendKey(byte keyCode) {
                                            Beeper sdkBeeper = GlobalDataHolder.getInstance().getDeviceEngine().getBeeper();
                                            sdkBeeper.beep(100);
                                            switch (keyCode) {
                                                case PinPadKeyCode.KEYCODE_CANCEL:
                                                    subject.onError(new OperationCanceledException());
                                                    break;
                                                case PinPadKeyCode.KEYCODE_CONFIRM:
                                                    subject.onCompleted();
                                                    break;
                                                default:
                                                    subject.onNext(keyCode);
                                                    break;
                                            }
                                        }
                                    });
                                } else {
                                    PinPad sdkPinPad = GlobalDataHolder.getInstance().getDeviceEngine().getPinPad();
                                    int result = sdkPinPad.inputText(timeout, new com.nexgo.oaf.apiv3.device.pinpad.OnPinPadInputListener() {
                                        @Override
                                        public void onInputResult(int i, byte[] bytes) {
                                            switch (i) {
                                                case SdkResult.Success:
                                                    subject.onCompleted();
                                                    break;
                                                case SdkResult.PinPad_Input_Cancel:
                                                    subject.onError(new OperationCanceledException());
                                                    break;
                                                default:
                                                    subject.onError(new Exception());
                                                    break;
                                            }
                                        }

                                        @Override
                                        public void onSendKey(byte b) {
                                            subject.onNext(b);
                                        }
                                    }, InputModeEnum.PLAINTEXT);
                                    if (result != SdkResult.Success) {
                                        subject.onError(new Exception());
                                    }
                                }
                            }
                        });
                    }
                })
                .filter(new Func1<Byte, Boolean>() {
                    @Override
                    public Boolean call(Byte aByte) {
                        return aByte != PinPadKeyCode.KEYCODE_STAR
                                && aByte != PinPadKeyCode.KEYCODE_OCTOTHORPE
                                && ((bos.size() < finalMax || aByte == PinPadKeyCode.KEYCODE_CLEAR));
                    }
                })
                .map(new Func1<Byte, Byte>() {
                    @Override
                    public Byte call(Byte aByte) {
                        if (aByte == PinPadKeyCode.KEYCODE_CLEAR) {
                            bos.reset();
                        } else {
                            bos.write(aByte);
                        }
                        if (aByte >= PinPadKeyCode.KEYCODE_0 && aByte <= PinPadKeyCode.KEYCODE_9) {
                            aByte = PinPadKeyCode.KEYCODE_STAR;
                        }
                        return aByte;
                    }
                })
                .doOnNext(new Action1<Byte>() {
                    @Override
                    public void call(Byte aByte) {
                        EventBus.getDefault().post(new PinKeyCodeEvent(aByte));
                    }
                })
                .doOnError(new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        EventBus.getDefault().post(new PinKeyCodeEvent(PinPadKeyCode.KEYCODE_CANCEL));
                    }
                })
                .doOnCompleted(new Action0() {
                    @Override
                    public void call() {
                        EventBus.getDefault().post(new PinKeyCodeEvent(PinPadKeyCode.KEYCODE_CONFIRM));
                    }
                })
                .finallyDo(new Action0() {
                    @Override
                    public void call() {
                        try {
                            bos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                })
                .subscribe(new Action1<Byte>() {
                    @Override
                    public void call(Byte aByte) {
                        listener.onSendKey(aByte);
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        int ret = 0;
                        if (throwable instanceof OperationCanceledException) {
                            ret = ServiceResult.PinPad_Input_Cancel;
                        } else if (throwable instanceof TimeoutException) {
                            ret = ServiceResult.PinPad_Input_Timeout;
                        } else {
                            ret = ServiceResult.PinPad_Other_Error;
                        }
                        listener.onInputResult(ret, null);
                    }
                }, new Action0() {
                    @Override
                    public void call() {
                        if (bos.toByteArray().length > 0) {
                            byte[] enPinData = PinPadUtils.getMobileModeEnPin(panBlock, bos.toByteArray(), context);
                            listener.onInputResult(ServiceResult.Success, enPinData);
                        } else {
                            listener.onInputResult(ServiceResult.PinPad_No_Pin_Input, new byte[8]);
                        }
                    }
                });
        return ServiceResult.Success;
    }
}
